package com.unitd.frame.sso.service.encrypt.impl;

import com.unitd.frame.comm.utils.encrypt.base64.UrlBase64;
import com.unitd.frame.sso.common.config.SSOConfig;
import com.unitd.frame.sso.common.encrypt.AlgorithmEnum;
import com.unitd.frame.sso.common.encrypt.MD5;
import com.unitd.frame.sso.exception.SsoException;
import com.unitd.frame.sso.service.encrypt.ISSOEncrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;

/**
 * @desc SSO 对称加密接口实现,支持DES、AES、Blowfish、RC2、RC4的加密解密.
 * 算法简介：
 * 1、Standard,即数据加密标准,是一种使用密钥加密的块算法,1976年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),
 * 随后在国际上广泛流传开来.
 * 2、AES,高级加密标准(英语：Advanced Encryption Standard,缩写：AES),在密码学中又称Rijndael加密法,是美国联邦政府采
 * 用的一种区块加密标准.这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用.经过五年的甄选流程,高级加密标准由
 * 美国国家标准与技术研究院(NIST) 于2001年11月26日发布于FIPS PUB197,并在2002年5月26日成为有效的标准.2006年,高级加密
 * 标准已然成为对称密钥加密中最流行的算法之一.
 * 3、Blowfish算法是一个64位分组及可变密钥长度的对称密钥分组密码算法,可用来加密64比特长度的字符串.32位处理器诞生后,
 * Blowfish算法因其在加密速度上超越了DES而引起人们的关注.Blowfish算法具有加密速度快、紧凑、密钥长度可变、可免费使用等
 * 特点,已被广泛使用于众多加密软件.
 * 4、RC2是由著名密码学家Ron Rivest设计的一种传统对称分组加密算法,它可作为DES算法的建议替代算法.它的输入和输出都是64
 * 比特.密钥的长度是从1字节到128字节可变,但目前的实现是8字节(1998年).
 * 5、RC4加密算法是大名鼎鼎的RSA三人组中的头号人物Ronald Rivest在1987年设计的密钥长度可变的流加密算法簇.之所以称其为簇,
 * 是由于其核心部分的S-box长度可为任意,但一般为256字节.该算法的速度可以达到DES加密的10倍左右,且具有很高级别的非线性.
 * RC4起初是用于保护商业机密的.但是在1994年9月,它的算法被发布在互联网上,也就不再有什么商业机密了.RC4也被叫做ARC4
 * (Alleged RC4——所谓的RC4),因为RSA从来就没有正式发布过这个算法.
 * @filename MySSOEncrypt.java
 * @copyright www.unitd.com
 * @author Hudan
 * @version 1.0
 * @date 2016/10/14
 */
public class MySSOEncrypt implements ISSOEncrypt {
	private static final Logger logger = LoggerFactory.getLogger(MySSOEncrypt.class.getName());
	private AlgorithmEnum algorithm = AlgorithmEnum.RC4;

	public MySSOEncrypt() {
	}

	public MySSOEncrypt(AlgorithmEnum algorithm) {
		this.algorithm = algorithm;
//		System.err.println("当前SSO服务端的加解密算法采用的是:" + algorithm.getKey());
	}

	/**
	 * @desc 字符串内容加密
	 * @param value 加密内容
	 * @param key   密钥
	 * @return 加密后的密文
	 * @throws Exception
	 */
	@Override
	public String encrypt(String value, String key) throws Exception {
		byte[] b = UrlBase64.encode(encrypt(algorithm, value.getBytes(SSOConfig.getSSOEncoding()), key));
		return new String(b, SSOConfig.getSSOEncoding());
	}

	/**
	 * @desc 字符串内容解密
	 * @param value 解密内容
	 * @param key   密钥
	 * @return 解密后的字符串文本
	 * @throws Exception
	 */
	@Override
	public String decrypt(String value, String key) throws Exception {
		byte[] b = decrypt(algorithm, UrlBase64.decode(value.getBytes(SSOConfig.getSSOEncoding())), key);
		return new String(b, SSOConfig.getSSOEncoding());
	}

	/**
	 * @desc 按照对应的算法,生成对应的加密密文键值
	 * @param algorithm 指定的算法类型
	 * @param strKey 需要加密的明文串
	 * @return 密文键值
	 * @throws Exception
	 */
	private Key toKey(AlgorithmEnum algorithm, String strKey) throws Exception {
	/* MD5 处理密钥 */
		byte[] key = MD5.md5Raw(strKey.getBytes(SSOConfig.getSSOEncoding()));
		if (AlgorithmEnum.DES == algorithm) {
			DESKeySpec dks = new DESKeySpec(key);
			SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm.getKey());
			return keyFactory.generateSecret(dks);
		}
		return new SecretKeySpec(key, algorithm.getKey());
	}


	/**
	 * @desc 加密
	 * @param algorithm 加解密算法类型
	 * @param data 加密数据
	 * @param key 加密盐值
	 * @return
	 */
	public byte[] encrypt(AlgorithmEnum algorithm, byte[] data, String key) {
		try {
			Cipher cipher = Cipher.getInstance(algorithm.toString());
			cipher.init(Cipher.ENCRYPT_MODE, this.toKey(algorithm, key));
			return cipher.doFinal(data);
		} catch (Exception e) {
			logger.error("Encrypt setKey is exception.");
			throw new SsoException(e);
		}
	}

	/**
	 * @desc  解密
	 * @param algorithm 加解密算法类型
	 * @param data 解密数据
	 * @param key 加密盐值
	 * @return
	 */
	public byte[] decrypt(AlgorithmEnum algorithm, byte[] data, String key) {
		try {
			Cipher cipher = Cipher.getInstance(algorithm.toString());
			cipher.init(Cipher.DECRYPT_MODE, this.toKey(algorithm, key));
			return cipher.doFinal(data);
		} catch (Exception e) {
			logger.error("Encrypt setKey is exception.");
			throw new SsoException(e);
		}
	}

	public AlgorithmEnum getAlgorithm() {
		return algorithm;
	}

	public void setAlgorithm(AlgorithmEnum algorithm) {
		this.algorithm = algorithm;
	}
}